package com.huatai.paas.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.huatai.common.api.R;
import com.huatai.common.config.MinioAPI;
import com.huatai.common.constant.MinioCommonBucket;
import com.huatai.common.util.BeanUtil;
import com.huatai.common.util.StringUtil;
import com.huatai.paas.constant.CommonConstant;
import com.huatai.paas.dto.BackChunkDto;
import com.huatai.paas.dto.UploadFileMessage;
import com.huatai.paas.dto.info.QueryFileListInfo;
import com.huatai.paas.dto.info.QueryModelManagementInfo;
import com.huatai.paas.entity.FileChunkEntity;
import com.huatai.paas.entity.FileListEntity;
import com.huatai.paas.mapper.FileChunkMapper;
import com.huatai.paas.service.FileChunkService;
import com.huatai.paas.service.FileListService;
import com.huatai.paas.service.UploadFileMessageService;
import com.huatai.paas.vo.CheckChunkVO;
import groovy.util.logging.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PipedReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @BelongsProject: nanming-saas
 * @BelongsPackage: com.huatai.saas.service.impl
 * @Author: hecaiy
 * @CreateTime: 2024-04-11  19:33
 * @Description: TODO
 * @Version: 1.0
 */
@Service
@Slf4j
public class FileChunkServiceImpl extends ServiceImpl<FileChunkMapper, FileChunkEntity> implements FileChunkService {

	@Autowired
	private MinioAPI minioAPI;

	@Autowired
	private FileListService fileListService;

	@Autowired
	private UploadFileMessageService uploadFileMessageService;

	@Value("${minio.PREURL}")
	private String minioUrl;

	//@Value("${upload.profile}")
	private String filePath;

	private final static String folderPath = "/file";

	/**
	 * 文件上传至本地
	 * 每一个上传块都会包含如下分块信息：
	 * chunkNumber: 当前块的次序，第一个块是 1，注意不是从 0 开始的。
	 * totalChunks: 文件被分成块的总数。
	 * chunkSize: 分块大小，根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。
	 * currentChunkSize: 当前块的大小，实际大小。
	 * totalSize: 文件总大小。
	 * identifier: 这个就是每个文件的唯一标示,md5码
	 * filename: 文件名。
	 * relativePath: 文件夹上传的时候文件的相对路径属性。
	 * 一个分块可以被上传多次，当然这肯定不是标准行为，但是在实际上传过程中是可能发生这种事情的，这种重传也是本库的特性之一。
	 * <p>
	 * 根据响应码认为成功或失败的：
	 * 200 文件上传完成
	 * 201 文加快上传成功
	 * 500 第一块上传失败，取消整个文件上传
	 * 507 服务器出错自动重试该文件块上传
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public int postFileUpload(BackChunkDto chunk, HttpServletResponse response) {
		int result = CommonConstant.UPDATE_FAIL;
		MultipartFile file = chunk.getFile();
		Path path = Paths.get(generatePath(filePath + folderPath, chunk));
		try {
			Files.write(path, chunk.getFile().getBytes());
			FileChunkEntity fileChunkEntity = BeanUtil.copyProperties(chunk, FileChunkEntity.class);
			result = this.baseMapper.insert(fileChunkEntity);
			//写入数据库
		} catch (IOException e) {
			e.printStackTrace();
			response.setStatus(507);
			return CommonConstant.UPDATE_FAIL;
		}
		return result;
	}

//	@Override
//	public int postFileUpload(BackChunkDto chunk, HttpServletResponse response) {
//		int result = CommonConstant.UPDATE_FAIL;
//		MultipartFile file = chunk.getFile();
//		Path path = Paths.get(generatePath(filePath + folderPath, chunk));
//		try {
//			Files.write(path, chunk.getFile().getBytes());
//			FileChunkEntity fileChunkEntity = BeanUtil.copyProperties(chunk, FileChunkEntity.class);
//			result = this.baseMapper.insert(fileChunkEntity);
//			//写入数据库
//		} catch (IOException e) {
//			e.printStackTrace();
//			response.setStatus(507);
//			return CommonConstant.UPDATE_FAIL;
//		}
//		return result;
//	}

	/**
	 * 文件分片上传到minio
	 * @param chunk
	 * @param response
	 * @return
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public int postFileUploadMinio(BackChunkDto chunk, HttpServletResponse response) {
		MultipartFile file = chunk.getFile();
		int result = CommonConstant.UPDATE_FAIL;
		String chunkName = chunk.getFileName().split("\\.")[0];
//		String originalFilename = chunkName;
		// 分片大于1 存在分片
//		if(chunk.getTotalChunks()!=1){
//			originalFilename = chunk.getChunkNumber()+chunkName;
//		}
		//不判断了，小于5M没分片
		String originalFilename = chunk.getChunkNumber()+chunkName;
		try {
			String fileName = chunk.getIdentifier() + "/" +originalFilename;
			minioAPI.uploadInputStream(MinioCommonBucket.GANGHUA_PUB_BUCKET_URL, fileName, file.getInputStream(), file.getContentType());
			FileChunkEntity fileChunkEntity = BeanUtil.copyProperties(chunk, FileChunkEntity.class);
			result = this.baseMapper.insert(fileChunkEntity);
			return result;
		} catch (Exception e) {
			log.error("分片文件上传异常："+e.getMessage());
			return CommonConstant.UPDATE_FAIL;
		}
	}

	/**
	 * 功能描述:生成块文件所在地址
	 *
	 * @param:
	 * @return:
	 * @author: xjd
	 * @date: 2020/7/28 16:10
	 */
	private String generatePath(String uploadFolder, BackChunkDto chunk) {
		StringBuilder sb = new StringBuilder();
		//文件夹地址/md5
		sb.append(uploadFolder).append("/").append(chunk.getIdentifier());
		//判断uploadFolder/identifier 路径是否存在，不存在则创建
		if (!Files.isWritable(Paths.get(sb.toString()))) {
			try {
				Files.createDirectories(Paths.get(sb.toString()));
			} catch (IOException e) {
				log.error(e.getMessage(), e);
			}
		}
		//文件夹地址/md5/文件名-1
		return sb.append("/")
			.append(chunk.getFileName())
			.append("-")
			.append(chunk.getChunkNumber()).toString();
	}

	/**
	 * 检查文件上传状态
	 * @param chunk
	 * @param response
	 * @return
	 */
	@Override
	public CheckChunkVO getFileUpload(BackChunkDto chunk, HttpServletResponse response) {
		CheckChunkVO vo = new CheckChunkVO();
		//检查该文件是否存在于filelist中,如果存在,直接返回skipUpload为true,执行闪传
		List<FileListEntity> backFilelists = fileListService.list(Wrappers.<FileListEntity>lambdaQuery().eq(FileListEntity::getIdentifier,chunk.getIdentifier()));
		if (backFilelists != null && !backFilelists.isEmpty()) {
			response.setStatus(HttpServletResponse.SC_CREATED);
			vo.setSkipUpload(true);
			vo.setUrl(minioUrl+"/"+backFilelists.get(0).getPath());
			if (StringUtil.isNotBlank(chunk.getFileType())){
				//校验文件是否已存在
				boolean status = uploadFileMessageService.checkFileData(chunk.getMd5Data(),chunk.getFileType());
				if (!status) {
					UploadFileMessage uploadFileMessage = new UploadFileMessage();
					uploadFileMessage.setFileType(chunk.getFileType());
					uploadFileMessage.setFileUrl("/" + backFilelists.get(0).getPath());
					uploadFileMessage.setMd5Data(chunk.getIdentifier());
					uploadFileMessage.setFileId(chunk.getId());
					uploadFileMessage.setCreateTime(new Date());
					uploadFileMessage.setUpdateTime(new Date());
					uploadFileMessageService.save(uploadFileMessage);
				}
			}
			return vo;
		}

		List<FileChunkEntity> backChunks = this.baseMapper.selectList(Wrappers.<FileChunkEntity>lambdaQuery().eq(FileChunkEntity::getIdentifier,chunk.getIdentifier()));
		//将已存在的块的chunkNumber列表返回给前端,前端会规避掉这些块
		if (backChunks != null && !backChunks.isEmpty()) {
			List<Integer> collect = backChunks.stream().map(FileChunkEntity::getChunkNumber).collect(Collectors.toList());
			vo.setUploaded(collect);
		}
		return vo;
	}

	/**
	 * 本地文件合并
	 * @param targetFile 要形成的文件名
	 * @param folder     要形成的文件夹地址
	 * @param filename   文件的名称
	 */
	@Override
	public void merge(String targetFile, String folder, String filename) {
		try {
			Files.createFile(Paths.get(targetFile));
			Files.list(Paths.get(folder))
				.filter(path -> !path.getFileName().toString().equals(filename))
				.sorted((o1, o2) -> {
					String p1 = o1.getFileName().toString();
					String p2 = o2.getFileName().toString();
					int i1 = p1.lastIndexOf("-");
					int i2 = p2.lastIndexOf("-");
					return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
				})
				.forEach(path -> {
					try {
						//以追加的形式写入文件
						Files.write(Paths.get(targetFile), Files.readAllBytes(path), StandardOpenOption.APPEND);
						//合并后删除该块
						Files.delete(path);
					} catch (IOException e) {
					}
				});
		} catch (IOException e) {}
	}

	@Override
	public IPage<QueryFileListInfo> queryFileList(Integer pageNum, Integer pageSize, String fileType) {
		IPage<QueryFileListInfo> pageInfo = uploadFileMessageService.queryFileList(pageNum,pageSize,fileType);
		return pageInfo;
	}

	@Override
	public boolean deleteFileById(Long id) {
		return uploadFileMessageService.deleteFileById(id);
	}

	@Override
	public R upload(MultipartFile file,String md5) {
		String oriName = file.getOriginalFilename();
		String type = file.getContentType();
		String[] split = oriName.split("\\.");
		String name = split[split.length-2]+"."+split[split.length-1];
		String fileName = "scene/"+md5+ "/"+ name;
		String filePath = "";
		try{
			filePath = minioAPI.uploadInputStream(MinioCommonBucket.GANGHUA_PUB_BUCKET_URL, fileName, file.getInputStream(), type);
		}catch (Exception e){
			e.printStackTrace();
		}

		FileListEntity fileListEntity = fileListService.queryFileByMd5(md5);
		if(fileListEntity == null){
			fileListEntity = new FileListEntity();
			fileListEntity.setFileName(fileName);
			fileListEntity.setIdentifier(md5);
			fileListEntity.setUploadTime(new Date());
			fileListEntity.setTotalSize(file.getSize());
			fileListEntity.setUrl(filePath);
			fileListEntity.setPath("/"+MinioCommonBucket.GANGHUA_PUB_BUCKET_URL+filePath.split(MinioCommonBucket.GANGHUA_PUB_BUCKET_URL)[1]);
			this.fileListService.save(fileListEntity);
		}

		return R.data(filePath);
	}

	/**
	 * bucket       桶名称
	 * chunkPath    分片文件存放路径
	 * chunchName   分片文件名称
	 * fileNamePath 合并后文件存放路径
	 * fileName	    合并后文件名称
	 * chunkNum      分片数量
	 * @return
	 */
	@Override
	public R merge(BackChunkDto chunk) throws Exception {
		String fileName = chunk.getFileName();
		String chunkName = fileName.split("\\.")[0];
		String compose = minioAPI.compose(MinioCommonBucket.GANGHUA_PUB_BUCKET_URL, chunk.getIdentifier(), chunkName, CommonConstant.fileNamePath+"/"+chunk.getIdentifier()
			, fileName, chunk.getTotalChunks(), chunk.getTotalSize());
		//文件合并后保存真实路径到文件列表库
		if (compose != null) {
			FileListEntity fileListEntity = new FileListEntity();
			fileListEntity.setFileName(fileName);
			fileListEntity.setIdentifier(chunk.getIdentifier());
			fileListEntity.setUploadTime(new Date());
			fileListEntity.setTotalSize(chunk.getTotalSize());
			String fileUrl = minioUrl+"/"+MinioCommonBucket.GANGHUA_PUB_BUCKET_URL+"/"+compose;
			fileListEntity.setUrl(fileUrl);
			fileListEntity.setPath("/"+MinioCommonBucket.GANGHUA_PUB_BUCKET_URL+"/"+compose);
			this.fileListService.save(fileListEntity);
//			if (StringUtil.isNotBlank(chunk.getFileType())){
//				UploadFileMessage uploadFileMessage = new UploadFileMessage();
//				uploadFileMessage.setFileType(chunk.getFileType());
//				uploadFileMessage.setFileUrl(compose);
//				uploadFileMessage.setMd5Data(chunk.getMd5Data());
//				uploadFileMessage.setFileId(fileListEntity.getId());
//				uploadFileMessage.setCreateTime(new Date());
//				uploadFileMessage.setUpdateTime(new Date());
//				uploadFileMessageService.save(uploadFileMessage);
//			}

			return R.data(fileListEntity);
		}
		return R.fail("");
	}
}
